#!/usr/bin/python
import unittest
import xmlrpclib
import os
import urllib
import urllib2
import cookielib
import cmd
import sys
import threading

try:
    import readline
except ImportError:
    pass

devserver=False

class EndeavorCmd(cmd.Cmd):
    _aliases={"ls":"list_games","join":"join_game",
              "start":"start_game", "play":"play_game","info":"game_info", "pb":"show player_board", 
              "buildings": "show building_stock", 
              "ship":"activate shipyard ship", 
              "occupy":"activate colonialhouse occupy", 
              "draw":"activate market draw",
              "attack":"activate barracks attack",
              "to":"activate tradeoffice",
              "to1":"activate tradeoffice draw",
              "to2":"activate tradeoffice draw_and_draw",
              "c":"activate cartographer",
              "c1":"activate cartographer ship",
              "c2":"activate cartographer ship_and_ship",
              "d":"activate docks",
              "gh":"activate guildhall",
              "f":"activate fortress",
              "s/d":"use_token s/d",
              "o/d":"use_token o/d"
             }

    #decorator
    def playmanager_call(f):
        def wrapper(*args, **kwds):
            self=args[0]
            self.timer.cancel()
            result=f(*args, **kwds)
            self.check_server_for_updates()
            return result        
        wrapper.__name__=f.__name__
        wrapper.__dict__=f.__dict__
        wrapper.__doc__=f.__doc__
        return wrapper

    #decorator
    def api_call(f):
        """Wraps a method in a try..except block to catch exceptions on calls to the XMLRPC api, and display any error messages if the call fails."""
        def wrapper(*args, **kwds):
            try:
                return f(*args, **kwds)
            except xmlrpclib.Fault, e:
                print "%s" % e.faultString
            except xmlrpclib.ProtocolError, e:
                if e.errcode == 302:
                    print "*** You are not logged in. Type login [user_email] [password] to log into the game server ***"            
                else:
                    raise e 
        wrapper.__name__=f.__name__
        wrapper.__dict__=f.__dict__
        wrapper.__doc__=f.__doc__
        return wrapper

    #decorator
    def require_current_game(f):
        """Checks that we are currently playing a game (self._current_game_id is not None), and if not displays and error message"""
        def wrapper(self, *args, **kwds):
            if not self._current_game_id:
                print "You must be playing a game to do that. Type play [game_id] to play a game"
            else:
                return f(self, *args, **kwds)
 
        wrapper.__name__=f.__name__
        wrapper.__dict__=f.__dict__
        wrapper.__doc__=f.__doc__
        return wrapper

    #decorator
    def require_args(*arg_type_list):
        """Checks that the command line args passed to the *original* cmd after being parsed by parse_text_args match the supplied *arg_type_list"""
        def decorator(f):
            def wrapper(self, *args, **kwds):
                i=0
                for required_type in arg_type_list:
                    if not isinstance(required_type, tuple):
                        required_type=(required_type,)
                    if len(self._typecast_args)>i:
                        if not type(self._typecast_args[i]) in required_type:
                            print "Was expecting argument %d to be %r, got %s" % (i+1,required_type, type(self._typecast_args[i]))
                            return None
                    else:
                        print "Was expecting at least %d argument(s), got %d" %(len(arg_type_list), len(self._typecast_args))
                        return None
                    i=i+1
                return f(self, *args, **kwds)
                    
            wrapper.__name__=f.__name__
            wrapper.__dict__=f.__dict__
            wrapper.__doc__=f.__doc__
            return wrapper
        return decorator
     
    def __init__(self, xmlrpcserverproxy, transport, autologin=False):
        self._proxy=xmlrpcserverproxy
        self._transport=transport            
        self._result=None        
        self._setup_api_methods()
        self._show_methods={}
        self._activate_methods={}
        self._use_token_methods={}
        self._debug=False
        self._autologin=autologin
        
        for a in dir(self):
            #automatically add help methods, if they don't already exist
            m=getattr(self, a)
            if callable(m) and a[0:3]=="do_":
                help_method_name="help_%s" % a[3:]
                if help_method_name not in dir(self):
                    setattr(self, help_method_name, self._default_help_method(m))
           
            # automatically compile a list of show methods
            
            if callable(m) and a[0:5]=="show_":
                self._show_methods[a]=m.__doc__

            # automatically compile a list of activate methods
            
            if callable(m) and a[0:9]=="activate_":
                self._activate_methods[a]=m.__doc__


            # automatically compile a list of use_token methods
            
            if callable(m) and a[0:4]=="use_" and a[-6:]=="_token":
                self._use_token_methods[a]=m.__doc__
                
        self._update_prompt(None)
        self._last_seen_timestamp=None
        
        cmd.Cmd.__init__(self)
        
    def preloop(self):
        #set up game polling timer
        self.timer=threading.Timer(5, self.check_server_for_updates)
        self.timer.start()

    def postloop(self):
        self.timer.cancel()
    
    def cleanup(self):
        print
        self.timer.cancel()
        
    @api_call
    def check_server_for_updates(self):
        if self._current_game_id:
            try:
                log=self._proxy.playinfo.log_info(self._current_game_id, self._last_seen_timestamp)
            except xmlrpclib.ProtocolError:
                print "(Attempt to get server update failed, trying again in 5 seconds ....)"
                log=None
            if log:
                gameflow_messages=[entry["message"] for entry in log if entry["type"]=="GameFlowLogEntry"]
                if (gameflow_messages):
                    print
                    for message in gameflow_messages:
                            print message
                    self.onecmd("nothing")
                debug_messages=[entry for entry in log if entry["level"]=="debug"]
                if (debug_messages and self._debug):
                    print
                    for entry in debug_messages:
                            print "%r" % entry
                    
                last_log_message=log.pop()
                self._last_seen_timestamp=last_log_message["timestamp"]
        self.timer=threading.Timer(5, self.check_server_for_updates)
        self.timer.start()
        
    def _default_help_method(self, m):
        return lambda: sys.stdout.write(m.__doc__+"\n")

    def precmd(self, line):
        """
        This is run before all commands. Sets up various permutations of the command line args in lists and
        checks the _alises property for alias commands and runs the associated command if found
        """
        # Check for aliases and rewrite the command
        self._line=[t.strip() for t in line.split()]
        if self._line:
            if self._line[0] in self._aliases.keys():
                line="%s %s" % (self._aliases[self._line[0]]," ".join(self._line[1:]))
                self._line=[t.strip() for t in line.split()]
            # Set up helper properties with collections of arguments to save doing it in every method
            self._args=self._parse_text_args(self._line[1:], False)
            self._typecast_args=self._parse_text_args(self._line[1:], True)
        
        return line

    def help_aliases(self):
        print "The following aliases are available for commands:"
        print "Command\t\t\tAlias"
        print "=======\t\t\t====="
        
        for a in self._aliases.items():            
            print "%s\t\t\t%s" % a 

    @api_call
    def emptyline(self):
        if self._current_game_id:
            game_info=self._proxy.gamemanager.game_info(self._current_game_id)
            if self._autologin and game_info["logged_in_player"] != game_info["current_player"] and game_info["current_player"] != None:
                current_player_email=[p["email"] for p in game_info["players"] if p["position"]==game_info["current_player"]][0]
                print "Logging in as %s" % current_player_email
                self._transport.login(current_player_email, "")
                game_info=self._proxy.gamemanager.game_info(self._current_game_id)
            self._update_prompt(game_info)
        else:
            print
    
 
    def do_nothing(self, line):
        print

    def do_debug(self, line):
        """debug [off]
        Shows debug level responses from game server. Mostly only useful for client development
        """
        if len(self._args)==1 and self._args[0] == "off":
            self._debug=False
            print "debug off"
        else:
            self._debug=True
            print "debug on"

    def do_autologin(self, line):
        """autologin [off]
        Automatically logs in as the active player when return is hit. Mostly only useful for on the development server 
        for testing, where no passwords are required
        """
        if len(self._args)==1 and self._args[0] == "off":
            self._autologin=False
            print "autologin off"
        else:
            self._autologin=True
            print "autologin on"
            
    @api_call
    @require_args(str)
    def do_api(self, args_text):
        """
        This method runs the underlying api xmlrpc function supplied on the command line.   
        """
        cmd=self._args[0]
        if not cmd in self._apimethods:
            print "Unknown Command %s" % cmd
        else:
            module,method = cmd.split(".")
            module_args=self._typecast_args[1:]
            self._result=getattr(getattr(self._proxy, module), method)(*module_args)
            print self._result

#    def complete_api(self, text, line, begidx, endidx):
#        #print text, line, begidx, endidx
#        return  list("gamemanager")
    
    def help_api(self):
        print "Runs Endeavor XMLRPC API command directly. Commands are: "
        for m in self._apimethods:
            print "\t%s" % m

    @api_call
    def do_list_games(self, args_text):
        """
        list_games [new] [mine]
        Lists all games on the server. 
        Optional arg [new] will list only new games. 
        [mine] will list only games that you have joined
        """
        new_only="new" in self._args
        user_only="mine" in self._args
        games_list=self._proxy.gamemanager.list_games(new_only, user_only)
        for g in games_list:
            print "(%s) %s (%s players, %s%s)" % (g['id'], g['name'],g['num_players'], "round %s" % g['round'] if g['round'] != 0 else "New Game", ", password protected" if g['requires_password'] else "")
    
    @api_call    
    def do_create_game(self, args_text):
        """
        create_game [name] [password]
        Creates a new game, automatically adding the current user as the game owner
        """
        name=(self._args[0] or raw_input("Name: ")).strip()
        password=self._args[1] if len(self._args) > 1 else None

        game_id=self._proxy.gamemanager.create_game(name, password)
        print 'New game %d, "%s" created' % (game_id, name)
    
    @api_call                  
    def do_game_info(self, args_text):
        """
        game_info [game_id]
        Displays detailed game info for game [game_id]
        """
        game_id=self._typecast(args_text or self._current_game_id or raw_input("Game Id: "))
        game_info=self._proxy.gamemanager.game_info(game_id)
        self._display_game_info(game_info)
        if game_info and self._current_game_id==game_info["game_id"]:
            self._update_prompt(game_info)
        
    @api_call
    def do_join_game(self, args_text):
        """
        join_game [game_id] [password]
        Adds the current user to the list of players for the game [game_id]
        """
        game_id=self._typecast_args[0] or self._typecast(raw_input("Game Id: "))
        password=self._args[1] if len(self._args) > 1 else None
        multicall=xmlrpclib.MultiCall(self._proxy)
        multicall.gamemanager.get_current_timestamp()
        multicall.gamemanager.join_game(game_id, password)
        self._last_seen_timestamp,game_info=multicall()
        if (game_info):
            print 'Joined game %d' % game_id
            self._update_prompt(game_info)
            self.onecmd("game_info")    
        
    @api_call
    @require_current_game
    def do_ready(self, args_text):
        """
        ready
        Marks the current user as ready
        """
        self._proxy.gamemanager.player_ready(self._current_game_id, True)

    @api_call
    def do_leave_game(self, args_text):
        """
        leave_game [game_id]
        Removes the current user from the list of players for the game [game_id]
        """
        game_id=self._typecast(args_text or self._current_game_id or raw_input("Game Id: "))
        self._proxy.gamemanager.leave_game(game_id)
        print 'Left game %d' % game_id
        if self._current_game_id==game_id:
            self._update_prompt(None)
                
    @api_call
    def do_delete_game(self, args_text):
        """
        delete game [game_id]
        Deletes the game [game_id]. Note that only the Game owner can delete a game.
        """
        
        game_id=self._typecast(args_text or raw_input("Game Id: "))
        self._proxy.gamemanager.delete_game(game_id)
        print 'Deleted game %d' % game_id

    @api_call
    def do_start_game(self, args_text):
        """
        start_game [game_id]
        Starts the game [game_id]
        """
        game_id=self._typecast(args_text or self._current_game_id or raw_input("Game Id: "))
        multicall=xmlrpclib.MultiCall(self._proxy)
        multicall.gamemanager.start_game(game_id)
        multicall.gamemanager.get_current_timestamp()
        game_info, self._last_seen_timestamp=multicall()

        print 'Started game %d' % game_id
        self._display_game_info(game_info)
        self._update_prompt(game_info)

            
    @api_call
    def do_play_game(self, args_text):
        """
        play_game [game_id]
        Sets the current game to the game [game_id]
        """
        game_id=self._typecast(args_text or raw_input("Game Id: "))
        #If we are currently playing a game, go back to the game manager
        if self._current_game_id:
            self.onecmd("manager")
            
        multicall=xmlrpclib.MultiCall(self._proxy)
        multicall.gamemanager.game_info(game_id)
        multicall.gamemanager.whoami()
        multicall.gamemanager.get_current_timestamp()
        game_info, me, self._last_seen_timestamp=multicall()

        if me["email"] not in (p["email"] for p in game_info["players"] if p["email"]):
            print "You are not in that game"
        else:
            self._update_prompt(game_info)
            self.onecmd("game_info")    

    def do_quit(self, line):
        """
        quit
        Quit back to the command line
        """
        return True
    
    def do_manager(self, args_text):
        """
        Stops playing the current game and returns to the Game Manager
        """
        if self._current_game_id!=None:
            print "Exiting Game %d" % self._current_game_id
        self._update_prompt(None)

    @require_args(str, (str,int,list,bool))        
    def do_login(self, args_text):
        username, password=(self._args+["",""])[:2]
        try:
            self._transport.login(username, password)
        except urllib2.HTTPError, e:
            print "Login failed"
        else:
            self._setup_api_methods()
            print 'Now logged in as "%s"' % username
        
    def help_login(self):
        print "login [user] [pass] Login to server as [user] with password [pass]" 

    @require_current_game    
    @require_args(str)
    @playmanager_call
    @api_call
    def do_say(self, line):
        """say [message]
        Broadcast message to all players of the current game
        """
        self._proxy.playmanager.say(self._current_game_id, line)

    
    @require_current_game    
    @require_args(str)
    @playmanager_call
    @api_call
    def do_build(self, line):
        """build [building]
        Build building suring the build phase
        """
        building=self._args[0]
        game_info = self._proxy.playmanager.build(self._current_game_id, building)
        self._update_prompt(game_info)

    @require_current_game    
    @require_args((str, list))
    @playmanager_call
    @api_call
    def do_pay(self, line):
        """pay [building,building....]
        pay salary during salary phase for supplied buildings
        """
        
        buildings=self._typecast_args[0]
        if not isinstance(buildings, list):
            buildings=[buildings]
        game_info = self._proxy.playmanager.pay(self._current_game_id, buildings)
        self._update_prompt(game_info)

    @require_current_game    
    @require_args(str, str)
    def do_use_token(self, args_text):
        """use a token"""
        action=self._args[1]
        if "use_%s_token" % action in self._use_token_methods:
            activate_method=getattr(self, "use_%s_token" % action)
            activate_method()
        else:
            print "Invalid action '%s'. Type 'help use_token' for usage details" % action

    def help_use_token(self):
        print "Use an action token:"
        for s in self._use_token_methods.keys():
            print self._use_token_methods[s]     

 
    @require_args(str, str, (str,int))
    @playmanager_call
    @api_call
    def use_draw_token(self):
        """use_token [s/d|o/d] draw [region_no|slavery] ... Use action token from your player board to draw a card"""
        token=self._args[0]
        if self._args[2]=="slavery":
            region_no=0
            slavery_pile=True
        else:
            region_no=self._typecast_args[2]
            slavery_pile=False
        game_info = self._proxy.playmanager.use_draw_token(self._current_game_id, token, region_no, slavery_pile)
        self._update_prompt(game_info)        

    @require_args(str, str, int)
    @playmanager_call
    @api_call
    def use_ship_token(self):
        """use_token [s/d|o/d] ship region_no ... Use action token from your player board to ship to a region"""
        token=self._args[0]
        region_no=self._typecast_args[2]
        game_info = self._proxy.playmanager.use_ship_token(self._current_game_id, token, region_no)
        self._update_prompt(game_info)        

    @require_args(str, str, int)
    @playmanager_call
    @api_call
    def use_occupy_token(self):
        """use_token o/d occupy city_no ... Use action token from your player board to occupy a city"""
        token=self._args[0]
        city_no=self._typecast_args[2]
        game_info = self._proxy.playmanager.use_occupy_token(self._current_game_id, token, city_no)
        self._update_prompt(game_info)        

    @require_args(str, str, int)
    @playmanager_call
    @api_call
    def use_attack_token(self):
        """use_token att attack city_no ... Use action token from your player board to attack a city"""
        token=self._args[0]
        city_no=self._typecast_args[2]
        game_info = self._proxy.playmanager.use_attack_token(self._current_game_id, token, city_no)
        self._update_prompt(game_info)        

    @require_args(str, str, str)
    @playmanager_call
    @api_call
    def use_pay_token(self):
        """use_token pay pay [building]  ... Use action token from your player board to pay salary on a building"""
        token=self._args[0]
        building=self._typecast_args[2]
        game_info = self._proxy.playmanager.use_pay_token(self._current_game_id, token, building)
        self._update_prompt(game_info)        

    @require_current_game    
    @require_args(str,str)
    @playmanager_call
    def do_activate(self, args_text):
        """activate a building"""
        #building=self._args[0]
        command=self._args[1]
        if "activate_building_%s" % command in self._activate_methods:
            activate_method=getattr(self, "activate_building_%s" % command)
            activate_method()
        else:
            print "Invalid item '%s'. Type 'help activate' for usage details" % self._args[0]

    def help_activate(self):
        print "Activate a building:"
        for s in self._activate_methods.keys():
            print self._activate_methods[s]     
 
    @require_args(str, str, int)
    @api_call
    def activate_building_ship(self):
        """activate [shipyard|guildhall|docks|cartographer] ship [region_no] .. Activate your building and ship to region [region_no]"""
        building=self._args[0]
        region_no=self._typecast_args[2]
        game_info = self._proxy.playmanager.activate_building_ship(self._current_game_id, building, region_no)
        self._update_prompt(game_info)

    @require_args(str, str, int)
    @api_call
    def activate_building_ship_and_ship(self):
        """activate cartographer ship_and_ship [region_no] .. Activate your building and ship to region [region_no] twice"""
        building=self._args[0]
        region_no=self._typecast_args[2]
        game_info = self._proxy.playmanager.activate_building_ship_and_ship(self._current_game_id, building, region_no)
        self._update_prompt(game_info)
        
    @require_args(str, str, (str,int))
    @api_call
    def activate_building_draw(self):
        """activate [market|guildhall|tradeoffice] draw [region_no|slavery] .. Activate your building and draw a card from region [region_no], or a slavery card from Europe"""
        building=self._args[0]
        if self._args[2]=="slavery":
            region_no=0
            slavery_pile=True
        else:
            region_no=self._typecast_args[2]
            slavery_pile=False
        game_info = self._proxy.playmanager.activate_building_draw(self._current_game_id, building, region_no, slavery_pile)
        self._update_prompt(game_info)

    @require_args(str, str, (str,int))
    @api_call
    def activate_building_draw_and_draw(self):
        """activate tradeoffice draw_and_draw [region_no|slavery] [slavery] .. Activate tradeoffice and draw two cards card from region [region_no], or slavery cards from Europe"""
        building=self._args[0]
        if self._args[2]=="slavery":
            region_no=0
            slavery_pile_1=True
        else:
            region_no=self._typecast_args[2]
            slavery_pile_1=False
        if len(self._args) > 3 and self._args[3]=="slavery":
            slavery_pile_2=True
        else:
            slavery_pile_2=False
        game_info = self._proxy.playmanager.activate_building_draw_and_draw(self._current_game_id, building, region_no, slavery_pile_1, slavery_pile_2)
        self._update_prompt(game_info)

    @require_args(str, str, int)
    @api_call
    def activate_building_occupy(self):
        """activate [colonialhouse|docks|fortress] occupy [city_no] .. Activate your building to occupy city [city no]"""
        building=self._args[0]
        city_no=self._typecast_args[2]
        game_info = self._proxy.playmanager.activate_building_occupy(self._current_game_id, building, city_no)
        self._update_prompt(game_info)

    @require_args(str, str, int, int)
    @api_call
    def activate_building_occupy_and_ship(self):
        """activate docks occupy_and_ship [city_no] [region_no] .. Activate your docks to ship to region [region_no] and occupy city [city no]"""
        building=self._args[0]
        city_no=self._typecast_args[2]
        region_no=self._typecast_args[3]
        game_info = self._proxy.playmanager.activate_building_occupy_and_ship(self._current_game_id, building, city_no, region_no)
        self._update_prompt(game_info)

    @require_args(str, str, int)
    @api_call
    def activate_building_attack(self):
        """activate [barracks|fortress] attack [city_no] .. Activate your building to attack city [city no]"""
        building=self._args[0]
        city_no=self._typecast_args[2]
        game_info = self._proxy.playmanager.activate_building_attack(self._current_game_id, building, city_no)
        self._update_prompt(game_info)

    @require_args(str, str, str)
    @api_call
    def activate_building_pay(self):
        """activate [exchange|museum|parliament] pay [building] .. Activate your building to pay salary on [building]"""
        building=self._args[0]
        target_building=self._typecast_args[2]
        game_info = self._proxy.playmanager.activate_building_pay(self._current_game_id, building, target_building)
        self._update_prompt(game_info)

    @require_current_game
    @playmanager_call
    @api_call
    def do_pass(self, line):
        """Pass to end your action round"""
        game_info = self._proxy.playmanager.pass_action(self._current_game_id)
        self._update_prompt(game_info)

    @require_current_game
    @require_args((str,list))
    @playmanager_call
    @api_call
    def do_discard(self, line):
        """discard cards if you have exceeded your card limit at the end of the round"""
        cards=self._typecast_args[0]
        if not isinstance(cards, list):
            cards=[cards]        
        game_info = self._proxy.playmanager.discard(self._current_game_id, cards)
        self._update_prompt(game_info)
 
    @require_current_game
    @require_args(str)
    def do_show(self, args_text):
        """show various elements of the game state"""
        if "show_%s" % self._args[0] in self._show_methods:
            show_method=getattr(self, "show_%s" % self._args[0])
            show_method()
        else:
            print "Invalid item '%s'. Type 'help show' for usage details" % self._args[0]
             
    def help_show(self):
        print "show [item] ..."
        print "Show various aspects of the game state. Items include:"
        for s in self._show_methods.keys():
            print ("%s:" % s[5:]).ljust(15),self._show_methods[s]     

    @require_current_game    
    @api_call
    def show_game_state(self):
        """Show the full game state, including game info, buildings, regions, cities and card piles"""
        game_id=self._current_game_id
        multicall=xmlrpclib.MultiCall(self._proxy)
        multicall.gamemanager.game_info(game_id)
        multicall.playinfo.boardstate_info(game_id)
        game_info, board_info=multicall()
        self._display_game_info(game_info)
        self._display_board_state(board_info)
        print
  
    @require_current_game
    @require_args(str, int)
    @api_call
    def show_region(self):
        """[region_no] Show info for region [region_no]"""
        game_id=self._current_game_id
        multicall=xmlrpclib.MultiCall(self._proxy)
        multicall.playinfo.region_info(game_id, self._typecast_args[1])
        multicall.playinfo.connection_info(game_id)
        region_info, connection_info = multicall()
        self._display_region(region_info, connection_info)
        print

    @require_current_game
    @api_call
    def show_building_stock(self):
        """Show the building stock"""
        game_id=self._current_game_id
        building_stock_info=self._proxy.playinfo.building_stock_info(game_id)
        self._display_building_stock(building_stock_info)
        print
 
    @require_current_game
    @api_call
    def show_connections(self):
        """Show connection details"""
        game_id=self._current_game_id
        multicall=xmlrpclib.MultiCall(self._proxy)
        multicall.playinfo.connection_info(game_id)
        multicall.playinfo.city_info(game_id)
        connection_info, city_info = multicall()
        self._display_connections(connection_info, city_info)
        print 

    @require_current_game
    @api_call
    def show_player_board(self):
        """[player_no] Show the player board for player"""
        game_id=self._current_game_id
        player_position=self._typecast_args[1] if len(self._typecast_args) >=2 else None
        multicall=xmlrpclib.MultiCall(self._proxy)
        multicall.playinfo.player_board_info(game_id, player_position)
        multicall.playinfo.player_info(game_id, player_position)
        player_board_info, player_info = multicall()
        self._display_player_board(player_board_info, player_info)
        print 

     
    def _display_game_info(self, game_info):
        print
        print game_info["name"]
        print ("Round: %s" % game_info["round"] or "New Game").ljust(18),"Phase: %s" % game_info["phase"] or "None"
        print "Players:".ljust(16),
        current_player_name=""
        for p in game_info["players"]:
            print ("%s%s%s%s%d:%s" %("*" if game_info["owner"]==p["position"] else " ",
                                   "s" if game_info["start_player"]==p["position"] else " ",
                                         "p" if p["has_passed"] else " ",
                                         ">" if game_info["current_player"]==p["position"] else " ",
                                         p["position"], p["colour"])).ljust(12),
            print "%s  (%s)" % ((p["name"] or "<Empty Player Slot>").ljust(15), p["email"]),                                                                           
            if game_info["round"]<1 and p["is_ready"]:
                print "READY"
            else:
                print
            print "".ljust(16),
            if game_info["current_player"]==p["position"]:
                current_player_name = p["nickname"]+"'s" if game_info["logged_in_player"] != p["position"] else "your"
        print 
        if game_info["round"] > 0:
            print "It is %s turn." % current_player_name
            print
        
        
        
    def _display_board_state(self, board_info, brief=False):
        print "Building Stock"
        print "=============="
        print
        self._display_building_stock(board_info["building_stock"])
        print
        print "Regions"
        print "======="
        print
        for r in board_info["regions"]:
                self._display_region(r, board_info["connections"])
         
    def _display_region(self, r, connections):
        print
        print "%d) %s" % (r["number"], r["name"])
        if "shipping_track" in r.keys():
            print "Shipping Track: ".ljust(18),
            for s in r["shipping_track"]:
                print ("[%s] " % s).ljust(7),
            print
        if "card_pile" in r.keys():
            self._display_card_pile("Card Pile:",r["card_pile"])
        if "slavery_card_pile" in r.keys():
            self._display_card_pile("Slavery Pile:", r["slavery_card_pile"])
        print
        title="Cities:"
        cities=r["cities"]
        while cities:
            self._display_cities(title, cities[0:3], connections)
            del cities[0:3]
            title=""
            print
        print

    def _display_card_pile(self, name, card_pile):
            print name.ljust(18),
            for c in card_pile:
                print c["card_id"].ljust(7),
            print
            print "".ljust(18),
            for c in card_pile:
                print ("[%s]" % c["bonus"]).ljust(7),
            print
            
    def _display_building_stock(self, building_stock):
        for b in building_stock:
            bonus="[%s]" % b["bonus"] if "bonus" in b.keys() else ""
            action="->%s" % b["action"] if "action" in b.keys() else ""
            name="%s (%s%s)" % (b["name"], bonus, action)       
            print "(%d)\t%s\t%d" % (b["level"], name.ljust(20), b["count"])

    def _display_cities(self, title, cities, connections):
        print title.ljust(18),
        for c in cities:
            print ("%d) %s" % (c["number"], c["name"])).ljust(15),
        print
        print "".ljust(18),
        for c in cities:
            info = "(%s)" % (c["occupier"]) if c["occupier"] else "[%s]" % (c["trade_token"] or " ")
            print info.ljust(15),
        print
        #create matrix of connections
        city_connections=dict([[str(c["number"]), []] for c in cities])
        
        for x in connections:
            if x["a_end"] in [c["number"] for c in cities]:
                content="(%s)" % x["occupier"] if x["occupier"] else "[%s]" % x["trade_token"] if x["trade_token"] else "[ ]"
                city_connections[str(x["a_end"])].append({"city_number":x["b_end"], "content":content})
            if x["b_end"] in [c["number"] for c in cities]:
                content="(%s)" % x["occupier"] if x["occupier"] else "[%s]" % x["trade_token"] if x["trade_token"] else "[ ]"
                city_connections[str(x["b_end"])].append({"city_number":x["a_end"], "content":content})

        max_rows=max([len(city_connections[x]) for x in city_connections.keys()])
        
        for i in range(max_rows):
            print "".ljust(18),
            for c in cities:
                x=city_connections[str(c["number"])]
                if i < len(x):
                    print ("|-->(%d) %s" % (x[i]["city_number"],x[i]["content"])).ljust(15),
                else:
                    print "".ljust(15),
            print
    
    def _display_connections(self, connections, cities):
        print "Connections:".ljust(18),
        for c in connections:
            content="(%s)" % c["occupier"] if c["occupier"] else "[%s]" % c["trade_token"] if c["trade_token"] else "[ ]"
            city_a = [city for city in cities if city["number"] == c["a_end"]][0]
            city_b = [city for city in cities if city["number"] == c["b_end"]][0]            
            print ("%d)" % city_a["number"]).ljust(4) + city_a["name"].ljust(14),
            print "<-----",content.ljust(7),"----->",
            print ("%d)" % city_b["number"]).ljust(4) + city_b["name"].ljust(14)
            print "".ljust(18),
        print

    def _display_player_board(self, player_board, player):
        print ("Player %d:" % player["position"]).ljust(18),player["colour"].ljust(15),player["nickname"].ljust(15)
        print
        print "Status:".ljust(18),("Industry: %d" % player_board["industry"]).ljust(15),
        print ("Culture: %d" % player_board["culture"]).ljust(15),
        print ("Finance: %d" % player_board["finance"]).ljust(15),
        print ("Politics: %d" % player_board["politics"]).ljust(15)
        print "".ljust(18),("Build Level: %d" % player_board["build_level"]).ljust(15),
        print ("Growth Level: %d" % player_board["growth_level"]).ljust(15),
        print ("Salary Level: %d" % player_board["salary_level"]).ljust(15), 
        print "Card Limit: %d" % player_board["card_limit"]
        print  " ".ljust(67) + "+ Free Gov"
        if player_board["card_limit"] < 5:
            print " ".ljust(66) +" + slavery" 
        print
        print "Harbour:".ljust(18),("Population: %d" % player_board["harbour_population"]).ljust(15),"Action Tokens: ",
        if player_board["harbour_tokens"]:
            for t in player_board["harbour_tokens"]:
                print "[%s]" % t,
            print
        else:
            print "None"
        print
        print "Buildings:".ljust(18),
        for b in player_board["buildings"]:
            bonus="[%s]" % b["bonus"] if b["bonus"] else ""
            action="->%s" % b["action"] if b["action"] else ""
            print b["name"].ljust(15),
            print ("(%s%s)" % (bonus, action)).ljust(15), "occupied" if b["occupied"] else "empty" if b["action"] else "-----"       
            print "".ljust(18),
        print
        print "Cards:".ljust(18),
        for c in player_board["cards"]:
            print ("%s%s[%s]" % (c["card_id"], " " if c["card_id"] else "", c["bonus"] or " ")).ljust(7),
        print
        print 
        if len(player_board["discarded_slavery_cards"]) > 0:
            print "Discarded Slavery Cards: %d" % len(player_board["discarded_slavery_cards"])
        print 
        
    def _update_prompt(self, game_info):
        if game_info:
            self._current_game_id=game_info["game_id"]
            self.prompt="(Game %d [%s]) %s" % (game_info["game_id"], "lobby" if game_info["phase"] < 1 else game_info["phase"], ">" if game_info["logged_in_player"]==game_info["current_player"] else "")
        else:
            self._current_game_id=None
            self.prompt="(Game Manager)# "
             

    def _setup_api_methods(self):
        try:
            self._apimethods=self._proxy.system.listMethods()
        except xmlrpclib.ProtocolError, e:
            self._handle_protocol_error(e)

    def _handle_protocol_error(self, e):
        """Wrapper method for calling methods on out xmlrpc proxy object and handle"""
        if e.errcode == 302:
            print "*** You are not logged in. Type login [user_email] [password] to log into the game server ***"            
        else:
            raise e            

    def _handle_fault(self, f):
        print "%s" % f.faultString
            
    def _parse_text_args(self, args, typecast=False):
        l = []

        if isinstance(args, str):
            args=[t.strip() for t in args.split()]
 
        for a in args:
            if not typecast:
                    l.append(a)
            else:
                l.append(self._typecast(a))               
        return l
    
    def _get_user_input(self, prompt):
        return raw_input(prompt).strip()
        
    def _typecast(self, a):
            if isinstance(a, basestring) and "," in a:
                return [self._typecast(arg) for arg in a.split(",") if arg]
            if a in ("True", "true"): 
                return True
            elif a in ("False", "false"):
                return False
            else:
                try:
                    return int(a)
                except ValueError:
                    return a
        
class GaeAuthTransport(xmlrpclib.Transport):
    """
    This is an xmlrpclib Transport object that allows setting the Google App Engine Auth cookie
    in requests. it also includes a help login function, that given a google login and password
    will request an auth_token from the google login page, and then pass this token to a google 
    app engine application and retrieve the authorised cookie from the response.
    """
    
    def __init__(self, serv_uri, login_uri, devserver=False):
        xmlrpclib.Transport.__init__(self)        
        self._authcookie=None
        self._devserver=devserver
        self._serv_uri=serv_uri
        self._login_uri=login_uri
        self._api_version="0.51"
        
    def request(self, host, handler, request_body, verbose=0):
        # issue XML-RPC request
        h = self.make_connection(host)
        if verbose:
            h.set_debuglevel(1)
        self.send_request(h, handler, request_body)
        self.send_host(h, host)
        # Set the API version here
        h.putheader("X-Endeavor-Api-Version",self._api_version)
        # Add the Authentication Cookie if we have one
        if not self._authcookie is None:
            h.putheader("Cookie", self._authcookie)
        self.send_user_agent(h)
        self.send_content(h, request_body)
        errcode, errmsg, headers = h.getreply()
        if errcode != 200:
            raise xmlrpclib.ProtocolError(
                host + handler,
                errcode, errmsg,
                headers
                )
        self.verbose = verbose
        try:
            sock = h._conn.sock
        except AttributeError:
            sock = None
        return self._parse_response(h.getfile(), sock)

    def login(self, user_email, password):
        if self._devserver:
            self.devserver_login(user_email, password)
        else:
            self.gae_login(user_email, password)
            
    def gae_login(self, user_email, password):
    
        # we use a cookie to authenticate with Google App Engine
        #  by registering a cookie handler here, this will automatically store the
        #  cookie returned when we use urllib2 to open serv_uri
        cookiejar = cookielib.LWPCookieJar()
        opener = urllib2.OpenerDirector()
        opener.add_handler(urllib2.ProxyHandler())
        opener.add_handler(urllib2.UnknownHandler())
        opener.add_handler(urllib2.HTTPHandler())
        opener.add_handler(urllib2.HTTPDefaultErrorHandler())
        opener.add_handler(urllib2.HTTPSHandler())
        opener.add_handler(urllib2.HTTPErrorProcessor())
        opener.add_handler(urllib2.HTTPCookieProcessor(cookiejar))
    
        urllib2.install_opener(opener)
    
        #
        # get an AuthToken from Google accounts
        #
        auth_uri = 'https://www.google.com/accounts/ClientLogin'
        authreq_data = urllib.urlencode({ "Email":   user_email,
                                          "Passwd":  password,
                                          "service": "ah",
                                          "source":  __file__,
                                          "accountType": "HOSTED_OR_GOOGLE" })
        auth_req = urllib2.Request(auth_uri, data=authreq_data)
        auth_resp = urllib2.urlopen(auth_req)
        auth_resp_body = auth_resp.read()
        # auth response includes several fields - we're interested in
        #  the bit after Auth=
        auth_resp_dict = dict(x.split("=") for x in auth_resp_body.split("\n") if x)
        authtoken = auth_resp_dict["Auth"]
        
        #
        # get a cookie
        #
        #  the call to request a cookie will also automatically redirect us to the page
        #   that we want to go to
        #  the cookie jar will automatically provide the cookie when we reach the
        #   redirected location
        # (And for any future requests)
           
        serv_args = {}
        serv_args['continue'] = self._serv_uri
        serv_args['auth']     = authtoken
        
        full_serv_uri = "%s?%s" % (self._login_uri, urllib.urlencode(serv_args))
        
        serv_req = urllib2.Request(full_serv_uri)
    
        try:
            response = opener.open(serv_req)
        except urllib2.HTTPError, e:
            response = e
        
        if (response.code != 302 or response.info()["location"] != self._serv_uri):
            raise urllib2.HTTPError(serv_req.get_full_url(), response.code, response.msg,response.headers, response.fp)

        self._authcookie=response.headers.dict['set-cookie'].split(';')[0]
        
    def devserver_login(self, user_email, password=None):
        self._authcookie='dev_appserver_login="%s":True; Path=/;' % user_email
               

user_email=""
user_password=""

if (devserver):
    user_email="grabags@gmail.com"
    user_password=""
    uri="http://192.168.0.3:8080/xmlrpc/"
    login_uri=""    
else:
    uri="http://endeavorserver.appspot.com/xmlrpc/"
    login_uri="http://endeavorserver.appspot.com/_ah/login"

transport=GaeAuthTransport(uri, login_uri, devserver)

try:
    transport.login(user_email, user_password)
except urllib2.HTTPError, e:
    pass


proxy = xmlrpclib.ServerProxy(uri, transport,allow_none=True)

parser=EndeavorCmd(proxy, transport, devserver)
try:
    parser.cmdloop("Welcome to the Endeavor Command Line Client")
except KeyboardInterrupt:
    parser.cleanup()
